package com.example.spider.proxy.core.tester;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ReUtil;
import com.example.spider.proxy.core.ProxyInfo;
import com.example.spider.util.FileBasedMap;
import com.example.spider.util.HeaderUtil;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.Proxy;
import java.net.SocketException;
import java.net.URL;
import java.net.URLConnection;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import static com.example.spider.proxy.core.tester.TestConstant.*;

/**
 * 测试是否能通过代理访问某个网站
 */
public class AbstractTestCallBack implements TestCallBack {

    @Qualifier("tester_ex_info")
    @Autowired
    protected FileBasedMap<ConcurrentHashMap<String, AtomicInteger>, String, AtomicInteger> autoReloadFileMap;

    /**
     * 获取当前ip地址，判断是否代理成功
     *
     * @param proxy proxy
     * @return proxy.ip
     */
    public static String getProxyIp(Proxy proxy, boolean init) {
        try {
            if (init) {
                int i = 0 / 0;
            }
            URLConnection httpCon = new URL("http://www.ip111.cn/").openConnection(proxy);
            DEFAULT_HEADER.forEach(httpCon::setRequestProperty);
            httpCon.setConnectTimeout(DEFAULT_TEST_TIME_OUT);
            httpCon.setReadTimeout(DEFAULT_TEST_TIME_OUT);
            String response = IoUtil.readUtf8(httpCon.getInputStream());
            List<String> ips = ReUtil.findAllGroup1("<p>(.*?)</p>", response);
            String ipInfo = ips.get(0);
            return ReUtil.findAllGroup1("(\\d+.\\d+.\\d+.\\d+)", ipInfo).get(0);
        } catch (Exception e) {
            // 初始化，不抛异常，安排后续任务初始化
            new Thread(() -> {
                try {
                    Thread.sleep(Duration.ofSeconds(5));
                } catch (InterruptedException ex) {
                    throw new RuntimeException(ex);
                }
                LOCAL_PUBLIC_IP = getProxyIp(Proxy.NO_PROXY, false);
            }).start();
            return null;
//            throw new RuntimeException(e);
        }
    }    protected static String LOCAL_PUBLIC_IP = getProxyIp(Proxy.NO_PROXY, true);

    protected final String aimUrl;

    public AbstractTestCallBack(String aimUrl) {
        this.aimUrl = aimUrl;
    }

    @Override
    public String getAimUrl(ProxyInfo proxyInfo) {
        return aimUrl;
    }

    @Override
    public Map<String, String> getHeaders() {
        return HeaderUtil.defaultHeaders();
    }

    @Override
    public boolean onResponse(ProxyInfo proxyInfo, int responseCode, String responseStr, long costTime) {
        boolean success = responseCode == DEFAULT_TEST_SUCCESS_CODE;
        if (success) {
            proxyInfo.success(costTime);
        } else {
            proxyInfo.fail(costTime);
        }
        return success;
    }

    @Override
    public boolean onException(ProxyInfo proxyInfo, long costTime, Exception e) {
        String exName = e.getClass().getSimpleName();
        String msg = Optional.of(e).map(Throwable::getCause).map(Throwable::getMessage).orElse(e.getMessage());
        String causeExName = Optional.of(e).map(Throwable::getCause).map(Object::getClass).map(Class::getSimpleName).orElse(null);

        proxyInfo.fail(DEFAULT_TEST_TIME_OUT);
        // 超时占99%，未认证等占 0.9%
        if (e instanceof IOException && e.getCause() instanceof IOException) {
            String invalidStr = "Server returned HTTP response code: 4";
            if (msg.contains(invalidStr)) {
                // 不再挣扎，直接丢弃
                proxyInfo.setInvalid(true);
                return false;
            }
        } else if (e instanceof FileNotFoundException) {
            // 404 不再挣扎，直接丢弃
            proxyInfo.setInvalid(true);
            return false;
        } else if (e instanceof SocketException) {
            if (e.getMessage().contains("Reply from SOCKS server has bad version")) {
                // 可能版本错了，下次尝试
                if (proxyInfo.getProxyTypeEnum() == ProxyTypeEnum.SOCKS4) {
                    proxyInfo.setProxyTypeEnum(ProxyTypeEnum.SOCKS5);
                } else {
                    proxyInfo.setProxyTypeEnum(ProxyTypeEnum.SOCKS4);
                }
                return false;
            } else if (e.getMessage().contains("Malformed reply from SOCKS server")) {
                // 可能不是socks代理
                if (proxyInfo.getProxyTypeEnum() == ProxyTypeEnum.SOCKS4 || proxyInfo.getProxyTypeEnum() == ProxyTypeEnum.SOCKS5) {
                    proxyInfo.setProxyTypeEnum(ProxyTypeEnum.HTTP);
                }
                return false;
            }

        }
        if (costTime < 5000) {
            LoggerFactory.getLogger(getClass()).warn("proxy({}:{}) TEST_FAIL cost{}ms. EX: {}-{} [msg:{}]",
                    proxyInfo.getIp(), proxyInfo.getPort(), costTime, exName,
                    causeExName, msg
            );
            // 分析代理失败错误原因分布
            String exKey = exName + "___" + causeExName + "___" + msg;
            autoReloadFileMap.computeIfAbsent(exKey, k -> new AtomicInteger()).incrementAndGet();
        }

        //        IOException-IOException [msg:Server returned HTTP response code
        // 401、403 407，需要认证丢弃代理、409 请求conflict（可能代理有约束）、412（特定缓存请求头不正确） 418爬虫警告、451法律禁止
        // 500 往往比较快，被拒绝，丢弃代理
        // 502/503 被拉黑，需要认证，丢弃代理
        // 504 网关超时可重试

        // SocketException-null 分布：
        // SocketException-null [msg:Unexpected end of file from server] 换类型
        // SocketException-null [msg:Connection reset] 忽略暂时

        //        ConnectException-null [msg:Connection refused: no further information] telnet ip:port 试试，估计大概率用不了，主机可能下线了，直接淘汰一段时间
        // [msg:Connection timed out: no further information] 重试
        // IORuntimeException___SocketTimeoutException 重试
        return false;
    }



}